package com.tylerhjones.autowallpaper;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class AWCore {
	private static Context context;
	private static final String TAG = "AWCore";

	private SharedPreferences sVal;
    private Editor sEdit;
    
	private WallpaperManager WM;
    
	private Common COM = new Common(); //Declare Common instance
    
	public AWCore(Context c) {
		context = c;
		
		sVal = context.getSharedPreferences(Common.SETTINGS, 0);
		sEdit = sVal.edit();
	}
	

	// *********************************************************************
	// Properties

	public boolean getFirstRun() {
		glog("FirstRun", COM.b2s(sVal.getBoolean(Common.CNF_FIRSTRUN, true)));
		return sVal.getBoolean(Common.CNF_FIRSTRUN, true);
	}

	public void setFirstRun(boolean val) {
		slog("FirstRun", COM.b2s(val));
		sEdit.putBoolean(Common.CNF_FIRSTRUN, val);
		sEdit.commit();
	}

	public boolean getRandom() {
		glog("Random", COM.b2s(sVal.getBoolean(Common.CNF_RANDOM, true)));
		return sVal.getBoolean(Common.CNF_RANDOM, true);
	}

	public void setRandom(boolean val) {
		slog("Random", COM.b2s(val));
		sEdit.putBoolean(Common.CNF_RANDOM, val);
		sEdit.commit();
	}

	public String getWPMode() {
		glog("WPMode", sVal.getString(Common.CNF_WPMODE, "center"));
		return sVal.getString(Common.CNF_WPMODE, "center");
	}

	public void setWPMode(String val) {
		slog("WPMode", val);
		sEdit.putString(Common.CNF_WPMODE, val);
		sEdit.commit();
	}

	public boolean getRunAtStartup() {
		glog("RunAtStartup", COM.b2s(sVal.getBoolean(Common.CNF_STARTUP, false)));
		return sVal.getBoolean(Common.CNF_STARTUP, false);
	}

	public void setRunAtStartup(boolean val) {
		slog("RunAtStartup", COM.b2s(val));
		sEdit.putBoolean(Common.CNF_STARTUP, val);
		sEdit.commit();
	}

	public void setImgSrcDir(String val) {
		slog("ImgSrcDir", val);
		sEdit.putString(Common.CNF_IMGSRC, val);
		sEdit.commit();
	}

	public String getImgSrcDir() {
		glog("ImgSrcDir", sVal.getString(Common.CNF_IMGSRC,
				"/mnt/sdcard/Pictures/"));
		return sVal.getString(Common.CNF_IMGSRC, "");
	}

	public void setCurImg(String val) {
		slog("CurImg", val);
		sEdit.putString(Common.CNF_CURIMG, val);
		sEdit.commit();
	}

	public String getCurImg() {
		glog("CurImg", sVal.getString(Common.CNF_CURIMG, ""));
		return sVal.getString(Common.CNF_CURIMG, "");
	}

	public int getTimerInterval() {
		glog("TimerInterval", Integer.toString(sVal.getInt(Common.CNF_INTERVAL,6)));
		return sVal.getInt(Common.CNF_INTERVAL, 6);
	}
	
	public void setTimerInterval(int val) {
		slog("TimerInterval", Integer.toString(val));
		sEdit.putInt(Common.CNF_INTERVAL, val);
		sEdit.commit();
	}

	public boolean getAutoChange() {
		glog("AutoChange", COM.b2s(sVal.getBoolean(Common.CNF_ACTIVE, false)));
		return sVal.getBoolean(Common.CNF_ACTIVE, false);
	}

	public void setAutoChange(boolean val) {
		slog("AutoChange", COM.b2s(val));
		sEdit.putBoolean(Common.CNF_ACTIVE, val);
		sEdit.commit();
	}
	
	public void setNumImages(int val) {
		slog("NumImages", Integer.toString(val));
		sEdit.putInt(Common.CNF_NUMIMAGES, val);
		sEdit.commit();
	}

	private void glog(String name, String val) {
		Log.i("AWCore - Properties", "GET setting '" + name + "': " + val);
	}

	private void slog(String name, String val) {
		Log.i("AWCore - Properties", "SET setting '" + name + "': " + val);
	}

	// END Properties Functions
	// *********************************************************************
	
	// *********************************************************************
	// Database functions

	public int getNumImages() {
		Database db = new Database(context);
		db.open();
		int i = db.NumImages();
		db.close();
		return i;
	}
	
	public boolean removeImage(String img) {
		Database db = new Database(context);
		db.open();
		boolean res = db.deleteImage(img);
		db.close();
		return res;
	}

	public boolean removeAllImages() {
		Database db = new Database(context);
		db.open();
		boolean res = db.deleteAllImages();
		db.close();
		return res;
	}

	public List<String> getAllImages() {
		Database db = new Database(context);
		Cursor cursor;
		db.open();
		List<String> list = new ArrayList<String>();
		cursor = db.Images();
		if (cursor.moveToFirst()) {
			do {
				list.add(cursor.getString(0));
			} while (cursor.moveToNext());
		}
		if (cursor != null && !cursor.isClosed()) {
			cursor.close();
		}
		db.close();
		return list;
	}

	public boolean addImages(File[] imgs) {
		Database db = new Database(context);
		db.open();
		if (imgs.length < 2) {
			db.addImage(imgs[0].getAbsolutePath().toString());
			Log.v(TAG, "Image added to DB!");
			return true;
		}

		File[] pimgs = imgs;
		Log.d(TAG, "Adding images to db, preparing...");
		File f;
		String p; 
		int sz = 0;
		int numimgs = getNumImages();
		Log.d(TAG, "Adding images to db [THREADED]....");
		//No more than 100 images at a time.
		if(numimgs < 100) {
			if((pimgs.length + numimgs) > 100) { sz = 100 - numimgs; } else { sz = pimgs.length; }
		} else {
			warnImagesLimit();
			return false;
		}
		ProgressDialog AddImagesDialog = ProgressDialog.show(context, "", "Adding images... ", true);
		for (int i = 0; i < sz; i++) {
			AddImagesDialog.setMessage("Adding images... (" + String.valueOf(i + 1) + " of " + String.valueOf(sz) + ")");
			p = pimgs[i].getAbsolutePath().toString();
			if(ValidateImage(p) && !db.ImageExists(p)) {
				f = new File(p);
				db.addImage(f.getAbsolutePath().toString());
				Log.v(TAG, "Successfully added image file '" + f.getAbsolutePath().toString() + "' to the database!");
			} else { Log.v("AWS.addImages", "Image was not added due to failure given by ValidateImage() function"); }
			// Progress bar tick
		}
		AddImagesDialog.dismiss();
		db.close();
		Toast.makeText(context,	"Successfully added " + String.valueOf(sz) + " images to the DB!", Toast.LENGTH_LONG).show();
		return true;
	}
	
	public void warnImagesLimit() {
		String str = "100 Image limit has been reached!";
		Toast.makeText(context,	str, Toast.LENGTH_LONG).show();
	}

	// END Database Functions
	// *********************************************************************
	
	// *********************************************************************
	// Validate images functions
	
	public boolean ValidateDir(String dir) {
		File theDir = new File(dir);
		if (!theDir.isDirectory() || !theDir.canRead()) {
			return false;
		}
		return true;
	}
	
	public boolean ValidateImage(String img) {
		File f = new File(img);
		if (!f.isFile() || !f.canRead()) {
			Log.w(TAG, "Image file: '" + img + "' does not exist or is unreadable (permissions?)");
			String str = "Image file '" + img + "' not found or unreadable!";
			Toast.makeText(context,	str, Toast.LENGTH_LONG).show();
			return false;
		}
		if (f.length() > 2024000) {
			Log.w(TAG, "Image file size too large! Maximum file size: 2Mb");
			return false;
		}
		if(!f.getAbsoluteFile().toString().toUpperCase().endsWith("PNG") && !f.getAbsoluteFile().toString().toUpperCase().endsWith("JPG")) {
			Log.w(TAG, "File is not an image file. Supported formats: PNG, JPG");
			return false;	
		}
		return true;
	}
	
	public void ValidateDBImages(List<String> imgs) {
		Database db = new Database(context);
		db.open();
		File f; String p;
		int numimgs = imgs.size();
		for (int i = 0; i < numimgs; i++) {
			p = imgs.get(i);
			if(ValidateImage(p)) {
				f = new File(p);
				db.addImage(f.getAbsolutePath().toString());
				Log.v(TAG, "Successfully validated image file '" + f.getAbsolutePath().toString() + "'!");
			} else { 
				Log.v("AWS.addImages", "Invalid image was found! Removing..."); 
				db.deleteImage(p);
			}
		}
		db.close();
	}
	// END Validate images functions
	// *********************************************************************

	// *********************************************************************
	// Image functions
	
	public void SetWallpaper(final File f) throws FileNotFoundException {
		WM = WallpaperManager.getInstance(context);
		//Drawable WMDrawable = WM.getDrawable();
		//final InputStream IS = new FileInputStream(f);		// if(set.getString(Common.CNF_WPMODE, "center") == "center") {
		final FileInputStream is = new FileInputStream(f);
		
		Thread BasicSetImageThread = new Thread(new Runnable() {
			//Bitmap image = BitmapFactory.decodeFile(f.getAbsolutePath().toString());

			@Override
			public void run() {
				//InputStream bis = new BufferedInputStream(is);
				//Decode image size
		        BitmapFactory.Options opt = new BitmapFactory.Options();
		        opt.inJustDecodeBounds = true;
		        try {
					BitmapFactory.decodeStream(new FileInputStream(f), null, opt);
				} catch (FileNotFoundException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				//Bitmap image = BitmapFactory.decodeStream(bis);
				Bitmap bitmap = null;
				try {
					Log.d("SetWallpaper", "Thread.run() started...");
					if (getWPMode() == "center") {
						Log.d("SetWallpaper", "Entered 'center' code block");
						WM.setStream(is);
					} else if (getWPMode().contains("fit_xy")) {
						Log.d("SetWallpaper", "Entered 'fit_xy' code block");
						BitmapFactory.Options bfOptions=new BitmapFactory.Options();
					    bfOptions.inDither=false;                     //Disable Dithering mode
					    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
					    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
					    bfOptions.inTempStorage=new byte[32 * 1024];
						Rect tR = new Rect();
						tR.bottom = WM.getDesiredMinimumHeight(); tR.right =  WM.getDesiredMinimumHeight();
						tR.left = 0; tR.top = 0;
						WM.setBitmap(bitmap = BitmapFactory.decodeFileDescriptor(is.getFD(), tR, bfOptions));
					} else if (getWPMode().contains("scale_x")) {
						Log.d("SetWallpaper", "Entered 'scale_x' code block");
						float ratio = opt.outWidth / opt.outHeight;
						int newWidth = (int) Math.round(opt.outHeight * ratio);
						WM.setBitmap(this.decodeFile(f, newWidth, WM.getDesiredMinimumHeight()));
					} else if (getWPMode().contains("scale_y")) {
						Log.d("SetWallpaper", "Entered 'scale_y' code block");
						float ratio = opt.outHeight / opt.outWidth;
						int newHeight = (int) Math.round(opt.outWidth * ratio);
						WM.setBitmap(this.decodeFile(f, WM.getDesiredMinimumWidth(), newHeight));
					} else if (getWPMode().contains("scale_xy")) {
						Log.d("SetWallpaper", "Entered 'scale_xy' code block");
						WM.setBitmap(this.decodeFile(f, WM.getDesiredMinimumWidth(), WM.getDesiredMinimumHeight()));
					}
					is.close();
					//image.recycle();
					//bitmap.recycle();
				} catch (IOException e) {
					Log.e(TAG, "An exception occured: " + e.getMessage());
					e.printStackTrace();
				}
				Log.d("SetWallpaper", "Thread finished!");
			}
			
			public Bitmap decodeFile(File f, int newWidth, int newHeight){
			    try {
			        //Decode image size
			        BitmapFactory.Options opt = new BitmapFactory.Options();
			        opt.inJustDecodeBounds = true;
			        BitmapFactory.decodeStream(new FileInputStream(f), null, opt);

			        //Find the correct scale value. It should be the power of 2.
			        int width_tmp = opt.outWidth, height_tmp = opt.outHeight;
			        int scale = 1;
			        while(true){
			        	if(width_tmp/2<newWidth || height_tmp/2<newHeight) { break; }
			        	width_tmp /= 2;
			        	height_tmp /= 2;
			        	scale *= 2;
			        }
			        //Decode with inSampleSize
			        BitmapFactory.Options newopt = new BitmapFactory.Options();
			        newopt.inSampleSize = scale;
			        return BitmapFactory.decodeStream(new FileInputStream(f), null, newopt);
			    } catch (FileNotFoundException e) { Log.e(TAG, "decodeFile: File not found exception!"); }
			    return null;
			}
		});
		//BasicSetImageThread.setPriority(3);
		try{
			BasicSetImageThread.start();
			Log.d("SetWallpaper", "Thread called...");
		} catch(IllegalThreadStateException e) {
			Log.e(TAG, "SetWallpaper: An IllegalThreadStateException was thrown: " + e.getMessage());
			e.printStackTrace();
		}
		}
	
	//decodes image and scales it to reduce memory consumption
	public Bitmap decodeFile(File f, int newWidth, int newHeight){
	    try {
	        //Decode image size
	        BitmapFactory.Options opt = new BitmapFactory.Options();
	        opt.inJustDecodeBounds = true;
	        BitmapFactory.decodeStream(new FileInputStream(f), null, opt);

	        //Find the correct scale value. It should be the power of 2.
	        int width_tmp = opt.outWidth, height_tmp = opt.outHeight;
	        int scale = 1;
	        while(true){
	        	if(width_tmp/2<newWidth || height_tmp/2<newHeight) { break; }
	        	width_tmp /= 2;
	        	height_tmp /= 2;
	        	scale *= 2;
	        }
	        //Decode with inSampleSize
	        BitmapFactory.Options newopt = new BitmapFactory.Options();
	        newopt.inSampleSize = scale;
	        return BitmapFactory.decodeStream(new FileInputStream(f), null, newopt);
	    } catch (FileNotFoundException e) { Log.e(TAG, "decodeFile: File not found exception!"); }
	    return null;
	}



	static Bitmap generateBitmap(Bitmap bm, int width,
			int height) {
		if (bm == null) {
			return bm;
		}
		bm.setDensity(DisplayMetrics.DENSITY_MEDIUM);

		// This is the final bitmap we want to return.
		// XXX We should get the pixel depth from the system (to match the
		// physical display depth), when there is a way.
		Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
		newbm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
		Canvas c = new Canvas(newbm);
		c.setDensity(DisplayMetrics.DENSITY_MEDIUM);
		Rect targetRect = new Rect();
		targetRect.left = targetRect.top = 0;
		targetRect.right = bm.getWidth();
		targetRect.bottom = bm.getHeight();

		int deltaw = width - targetRect.right;
		int deltah = height - targetRect.bottom;

		if (deltaw > 0 || deltah > 0) {
			// We need to scale up so it covers the entire
			// area.
			float scale = 1.0f;
			if (deltaw > deltah) {
				scale = width / (float) targetRect.right;
			} else {
				scale = height / (float) targetRect.bottom;
			}
			targetRect.right = (int) (targetRect.right * scale);
			targetRect.bottom = (int) (targetRect.bottom * scale);
			deltaw = width - targetRect.right;
			deltah = height - targetRect.bottom;
		}

		targetRect.offset(deltaw / 2, deltah / 2);
		Paint paint = new Paint();
		paint.setFilterBitmap(true);
		paint.setDither(true);
		c.drawBitmap(bm, null, targetRect, paint);

		bm.recycle();
		return newbm;
	}
	
	public Bitmap generateThumbBitmap(Bitmap bm, int width, int height) {
		if (bm == null) {
			return bm;
		}
		bm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
		// This is the final bitmap we want to return.
		// XXX We should get the pixel depth from the system (to match the
		// physical display depth), when there is a way.
		Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
		newbm.setDensity(DisplayMetrics.DENSITY_MEDIUM);
		Canvas c = new Canvas(newbm);
		c.setDensity(DisplayMetrics.DENSITY_MEDIUM);
		Rect targetRect = new Rect();
		targetRect.left = targetRect.top = 0;
		targetRect.right = 48;
		targetRect.bottom = 48;

		int deltaw = width - targetRect.right;
		int deltah = height - targetRect.bottom;

		if (deltaw > 0 || deltah > 0) {
			// We need to scale up so it covers the entire
			// area.
			float scale = 1.0f;
			if (deltaw > deltah) {
				scale = width / (float) targetRect.right;
			} else {
				scale = height / (float) targetRect.bottom;
			}
			targetRect.right = (int) (targetRect.right * scale);
			targetRect.bottom = (int) (targetRect.bottom * scale);
			deltaw = width - targetRect.right;
			deltah = height - targetRect.bottom;
		}

		//targetRect.offset(deltaw / 2, deltah / 2);
		Paint paint = new Paint();
		paint.setFilterBitmap(true);
		paint.setDither(true);
		c.drawBitmap(bm, null, targetRect, paint);

		bm.recycle();
		return newbm;
	}
	
}
